package com.thinkit.cms.service.upload.client;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.http.HttpUtil;
import com.github.tobato.fastdfs.service.AppendFileStorageClient;
import com.google.common.collect.Lists;
import com.thinkit.cms.api.site.SiteService;
import com.thinkit.cms.dto.resource.SysResourceDto;
import com.thinkit.cms.dto.upload.Chunk;
import com.thinkit.core.annotation.DefaultUploadClient;
import com.thinkit.core.base.BaseContextKit;
import com.thinkit.core.constant.Constants;
import com.thinkit.nosql.base.BaseRedisService;
import com.thinkit.utils.model.ApiResult;
import com.thinkit.utils.properties.ThinkItProperties;
import com.thinkit.utils.utils.Checker;
import com.thinkit.utils.utils.Md5;
import com.thinkit.utils.utils.SnowflakeIdWorker;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;

@Component
public class LocalClient extends UploadClient {

    @Autowired
    BaseRedisService baseRedisService;

    private Map<String, String> contentMap = new HashMap<>(16);

    @Autowired
    AppendFileStorageClient appendFileStorageClient;

    @Autowired
    private ThinkItProperties thinkItProperties;

    @Autowired
    SiteService siteService;

    private String getUploadPath() {
        String siteId = BaseContextKit.getSiteId();
        String domainPath = siteService.getDomain(null, siteId);
        String root = thinkItProperties.getSourcePath() + Constants.localtionUploadPattern + domainPath + Constants.SEPARATOR;
        if (!FileUtil.exist(root)) {
            FileUtil.mkdir(root);
        }
        return root;
    }


    @Override
    public ApiResult uploadFile(String remoteFile) {
        String suffix = FileUtil.getSuffix(remoteFile);
        if (Checker.BeBlank(suffix)) {
            suffix = ".jpg";
        } else {
            suffix = "." + suffix;
        }
        String root = getUploadPath(), uuid = UUID.randomUUID().toString();
        String fileName = "file" + Constants.SEPARATOR + DateUtil.format(new Date(), "yyyyMMdd") + Constants.SEPARATOR + uuid + suffix;
        long size = HttpUtil.downloadFile(remoteFile, new File(root + fileName));
        Map<String, Object> uploadRes = new HashMap<>();
        uploadRes.put("filePath", root + fileName);
        uploadRes.put("fileFullPath", Constants.HTTP + siteService.getDomain(null, BaseContextKit.getSiteId()) + Constants.localtionUploadPattern + fileName);
        uploadRes.put("group", "file");
        uploadRes.put("path", fileName);
        uploadRes.put("fileName", fileName);
        uploadRes.put("fileMd5", Md5.md5(fileName));
        uploadRes.put("finshed", true);
        return ApiResult.result(uploadRes);
    }

    @Override
    public ApiResult uploadFile(MultipartFile multipartFile) {
        try {
            String root = getUploadPath();
            String fileName = getFileName(multipartFile);
            FileUtil.writeFromStream(multipartFile.getInputStream(), root + fileName);
            Map<String, Object> uploadRes = new HashMap<>();
            uploadRes.put("filePath", fileName);
            uploadRes.put("fileFullPath", Constants.HTTP + siteService.getDomain(null, BaseContextKit.getSiteId()) + Constants.localtionUploadPattern + fileName);
            uploadRes.put("group", getGroup(multipartFile));
            uploadRes.put("path", fileName);
            uploadRes.put("fileName", multipartFile.getOriginalFilename());
            uploadRes.put("fileMd5", Md5.md5(multipartFile.getInputStream()));
            uploadRes.put("finshed", true);
            return ApiResult.result(uploadRes);
        } catch (Exception e) {
            return ApiResult.result(20004);
        }
    }

    @Override
    public ApiResult keepUploadFile(Chunk chunk) {
        Map<String, Object> params = new HashMap<>();
        MultipartFile multipartFile = chunk.getFile();
        String root = getUploadPath();
        try {
            if (!checkIsUpload(chunk)) {
                String fileName = getFileName(multipartFile);
                boolean isLastChunk = chunk.getChunkNumber().intValue() == chunk.getTotalChunks().intValue();
                if (chunk.getTotalChunks().intValue() == 1) {
                    FileUtil.writeFromStream(multipartFile.getInputStream(), root + fileName);
                    params.put("fileMd5", chunk.getIdentifier());
                    params.put("fileName", multipartFile.getOriginalFilename());
                    params.put("filePath", fileName);
                    params.put("path", fileName);
                    params.put("fileFullPath", Constants.HTTP + siteService.getDomain(null, BaseContextKit.getSiteId()) + Constants.localtionUploadPattern + fileName);
                    params.put("group", getGroup(multipartFile));
                    params.put("finshed", true);
                } else {
                    String pathKey = Constants.fastDfsKeepUpload + chunk.getIdentifier() + "_path";
                    if (chunk.getChunkNumber() == 1) {
                        FileUtil.writeFromStream(multipartFile.getInputStream(), root + fileName);
                        markUpload(chunk, isLastChunk);
                        baseRedisService.set(pathKey, fileName);
                    } else {
                        Object fileNameObj = baseRedisService.get(pathKey);
                        if (Checker.BeNotNull(fileNameObj)) {
                            uploadAppend(root + fileNameObj.toString(), multipartFile.getBytes());
                            markUpload(chunk, isLastChunk);
                            params.put("finshed", isLastChunk);
                            if (isLastChunk) {
                                params.put("fileMd5", chunk.getIdentifier());
                                params.put("fileName", multipartFile.getOriginalFilename());
                                params.put("filePath", fileNameObj.toString());
                                params.put("path", fileNameObj.toString());
                                params.put("fileFullPath", Constants.HTTP + siteService.getDomain(null, BaseContextKit.getSiteId()) + Constants.localtionUploadPattern + fileNameObj.toString());
                                params.put("group", getGroup(multipartFile));
                            }
                        }
                    }
                }
            }
            return ApiResult.result(params);
        } catch (Exception e) {
            return ApiResult.result(20004);
        }
    }

    public static void uploadAppend(String filePath, byte[] bytes) throws IllegalStateException, IOException {
        File file = new File(filePath);
        if (file.exists() && file.isFile()) {
            FileOutputStream out = new FileOutputStream(filePath, true);
            out.write(bytes);
            out.flush();
            out.close();
        }
    }

    private boolean checkIsUpload(Chunk chunk) {
        String fileKey = Constants.fastDfsKeepUpload + chunk.getIdentifier();
        List<Integer> uploadChunk = (List<Integer>) baseRedisService.get(fileKey);
        if (Checker.BeNotEmpty(uploadChunk)) {
            Integer curetChunk = chunk.getChunkNumber();
            return uploadChunk.contains(curetChunk);
        } else {
            return false;
        }
    }

    private boolean markUpload(Chunk chunk, boolean isLastChunk) {
        String fileKey = Constants.fastDfsKeepUpload + chunk.getIdentifier();
        String pathKey = Constants.fastDfsKeepUpload + chunk.getIdentifier() + "_path";
        if (isLastChunk) {
            baseRedisService.removeByKeys(Arrays.asList(fileKey, pathKey));
        } else {
            List<Integer> uploadChunk = (List<Integer>) baseRedisService.get(fileKey);
            if (Checker.BeEmpty(uploadChunk)) {
                uploadChunk = new ArrayList<>();
            }
            uploadChunk.add(chunk.getChunkNumber());
            baseRedisService.set(fileKey, uploadChunk);
        }
        return true;
    }

    @Override
    public ApiResult readyUpload(MultipartFile file) {
        if (Checker.BeNotNull(file)) {
            return ApiResult.result();
        }
        return ApiResult.result(20026);
    }

    @Override
    public ApiResult uploadSuccess(ApiResult result, MultipartFile file) {
        return saveFileToDb(result, file);
    }

    @Override
    public void uploadError(ApiResult result) {

    }

    @Override
    public ApiResult deleteFile(Map<String, String> params) {
        String uid = params.get("fileUid");
        SysResourceDto resourceDto = getFileByUid(uid);
        if (Checker.BeNotNull(resourceDto)) {
            String filePath = resourceDto.getFilePath();
            if (Checker.BeNotBlank(filePath)) {
                filePath = getUploadPath() + File.separator + filePath;
                FileUtil.del(filePath);
                deleteFileByUid(uid);
                return ApiResult.result(filePath);
            } else {
                return ApiResult.result(20018);
            }
        }
        return ApiResult.result();
    }

    @Override
    public Map<String, Object> checkFileIsExistByMd5(String md5) {
        SysResourceDto resource = checkerHasFileByMd5(md5);
        Map<String, Object> params = new HashMap<>(16);
        if (Checker.BeNotNull(resource)) {
            params.put("filePath", resource.getFilePath());
            params.put("fileFullPath", resource.getFileFullPath());
            params.put("fileName", resource.getFileName());
            params.put("fileUid", resource.getFileUid());
            params.put("id", resource.getId());
            params.put("group", resource.getGroupName());
            params.put("path", resource.getFilePath());
            params.put("skip", true);
        } else {
            params.put("skip", false);
            String fileKey = Constants.fastDfsKeepUpload + md5;
            List<Integer> uploadChunk = (List<Integer>) baseRedisService.get(fileKey);
            params.put("uploadChunk", Checker.BeNotEmpty(uploadChunk) ? uploadChunk : Lists.newArrayList());
        }
        return params;
    }


    private String getContentMap(String contentType) {
        if (contentMap.isEmpty()) {
            contentMap.put("text/html", "html");
            contentMap.put("ext/plain", "text");
            contentMap.put("text/xml", "xml");
            contentMap.put("image/gif", "image");
            contentMap.put("image/jpeg", "image");
            contentMap.put("image/png", "image");
            contentMap.put("application/xhtml+xml", "xml");
            contentMap.put("application/pdf", "document");
            contentMap.put("application/msword", "document");
            contentMap.put("application/octet-stream", "file");
        }
        String type = contentMap.get(contentType);
        return Checker.BeNotBlank(type) ? type : "file";
    }

    private String getGroup(MultipartFile multipartFile) {
        return getContentMap(multipartFile.getContentType());
    }

    private String getFilePath(MultipartFile multipartFile) {
        String filePath = getGroup(multipartFile) + Constants.SEPARATOR +
                DateUtil.format(new Date(), "yyyyMMdd") + Constants.SEPARATOR;
        return filePath;
    }

    private String getFileName(MultipartFile multipartFile) {
        return getFilePath(multipartFile) + SnowflakeIdWorker.getId() + Constants.DOT +
                FileUtil.getSuffix(multipartFile.getOriginalFilename());
    }
}
